package edu.pucsp.predatorprey.simulador.erlang;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangInt;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangLong;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangRangeException;
import com.ericsson.otp.erlang.OtpErlangTuple;

import edu.pucsp.predatorprey.simulador.api.SaidaSimulacao;
import edu.pucsp.predatorprey.simulador.impl.InformacaoCiclo;
import edu.pucsp.predatorprey.simulador.java.criatura.AbstractCriatura;
import edu.pucsp.predatorprey.simulador.java.criatura.Predador;
import edu.pucsp.predatorprey.simulador.java.criatura.Presa;
import edu.pucsp.predatorprey.simulador.java.posicao.impl.Posicao;

public enum ProcessadorMensagem {

	INICIOU("simulacaoIniciou") {
		@Override
		public void processarMensagem(OtpErlangTuple msg, SaidaSimulacao saida, SimuladorErlang simulador) {
			OtpErlangList list = (OtpErlangList) msg.elementAt(1);
			Map<AbstractCriatura, Posicao> posicaoCriaturas = new HashMap<AbstractCriatura, Posicao>(list.arity());
			Iterator<OtpErlangObject> iterator = list.iterator();
			while (iterator.hasNext()) {
				try {
					OtpErlangTuple t = (OtpErlangTuple) iterator.next();
					OtpErlangAtom tipoCriatura = (OtpErlangAtom) t.elementAt(0);
					OtpErlangTuple posicao = (OtpErlangTuple) t.elementAt(1);
					int x = ((OtpErlangLong) posicao.elementAt(0)).intValue();
					int y = ((OtpErlangLong) posicao.elementAt(1)).intValue();
					AbstractCriatura criatura;
					if (tipoCriatura.atomValue().equals("predador")) {
						criatura = new Predador();
					} else if (tipoCriatura.atomValue().equals("presa")) {
						criatura = new Presa();
					} else {
						throw new SimuladorErlangException("Não foi possível determinar o tipo da criatura ao nascer");
					}
					posicaoCriaturas.put(criatura, new Posicao(x, y));
				} catch (OtpErlangRangeException e) {
					throw new SimuladorErlangException("Não foi possivel obter a posicao da criatura", e);
				}
			}
			saida.simulacaoIniciou(posicaoCriaturas);
		}
	},
	PAROU("simulacaoParou") {
		@Override
		public void processarMensagem(OtpErlangTuple msg, SaidaSimulacao saida, SimuladorErlang simulador) {
			simulador.executando(false);
			simulador.derrubarNode();
			saida.simulacaoParou();
		}
	},
	EXECUCOES_SIMULTANEAS("numeroExecucoesSimultaneas") {
		@Override
		public void processarMensagem(OtpErlangTuple msg, SaidaSimulacao saida, SimuladorErlang simulador) {
			try {
				OtpErlangInt numExecucoes = (OtpErlangInt) msg.elementAt(1);
				saida.numeroExecucoesSimultaneas(numExecucoes.intValue());
			} catch (ClassCastException e) {
				throw new SimuladorErlangException("Tipo inválido ao tentar obter o numero de execuções", e);
			} catch (OtpErlangRangeException e) {
				throw new SimuladorErlangException("Não foi possivel obter o numero de execucoes simultaneas", e);
			}
		}
	},
	CICLO_COMPLETO("cicloCompleto") {
		@Override
		public void processarMensagem(OtpErlangTuple msg, SaidaSimulacao saida, SimuladorErlang simulador) {
			try {
				OtpErlangLong ciclo = (OtpErlangLong) msg.elementAt(1);
				OtpErlangLong numeroPresas = (OtpErlangLong) msg.elementAt(2);
				OtpErlangLong numeroPredadores = (OtpErlangLong) msg.elementAt(3);
				saida.cicloCompleto(new InformacaoCiclo(ciclo.intValue(), numeroPresas.intValue(), numeroPredadores.intValue()));
			} catch (ClassCastException e) {
				throw new SimuladorErlangException("Tipo inválido ao tentar obter informação Ciclo", e);
			} catch (OtpErlangRangeException e) {
				throw new SimuladorErlangException("Não foi possivel obter informaçãoCiclo", e);
			}
		}
	},
	NASCEU("criaturaNasceu") {
		@Override
		public void processarMensagem(OtpErlangTuple msg, SaidaSimulacao saida, SimuladorErlang simulador) {
			try {
				OtpErlangAtom tipoCriatura = (OtpErlangAtom) msg.elementAt(1);
				OtpErlangTuple posicao = (OtpErlangTuple) msg.elementAt(2);
				int x = ((OtpErlangLong) posicao.elementAt(0)).intValue();
				int y = ((OtpErlangLong) posicao.elementAt(1)).intValue();
				AbstractCriatura criatura;
				if (tipoCriatura.atomValue().equals("predador")) {
					criatura = new Predador();
				} else if (tipoCriatura.atomValue().equals("presa")) {
					criatura = new Presa();
				} else {
					throw new SimuladorErlangException("Não foi possível determinar o tipo da criatura ao nascer");
				}
				saida.criaturaNasceu(criatura, new Posicao(x, y));
			} catch (ClassCastException e) {
				throw new SimuladorErlangException("Tipo inválido ao tentar converter criatura", e);
			} catch (OtpErlangRangeException e) {
				throw new SimuladorErlangException("Não foi possivel obter a posicao da criatura", e);
			}
		}
	},
	MORREU("criaturaMorreu") {
		@Override
		public void processarMensagem(OtpErlangTuple msg, SaidaSimulacao saida, SimuladorErlang simulador) {
			try {
				OtpErlangAtom tipoCriatura = (OtpErlangAtom) msg.elementAt(1);
				OtpErlangTuple posicao = (OtpErlangTuple) msg.elementAt(2);
				int x = ((OtpErlangLong) posicao.elementAt(0)).intValue();
				int y = ((OtpErlangLong) posicao.elementAt(1)).intValue();
				AbstractCriatura criatura;
				if (tipoCriatura.atomValue().equals("predador")) {
					criatura = new Predador();
				} else if (tipoCriatura.atomValue().equals("presa")) {
					criatura = new Presa();
				} else {
					throw new SimuladorErlangException("Não foi possível determinar o tipo da criatura ao morrer");
				}
				saida.criaturaMorreu(criatura, new Posicao(x, y));
			} catch (ClassCastException e) {
				throw new SimuladorErlangException("Tipo inválido ao tentar converter criatura", e);
			} catch (OtpErlangRangeException e) {
				throw new SimuladorErlangException("Não foi possivel obter a posicao da criatura", e);
			}
		}
	},
	MOVEU("criaturaMoveu") {
		@Override
		public void processarMensagem(OtpErlangTuple msg, SaidaSimulacao saida, SimuladorErlang simulador) {
			try {
				OtpErlangTuple posicao = (OtpErlangTuple) msg.elementAt(1);
				int x = ((OtpErlangLong) posicao.elementAt(0)).intValue();
				int y = ((OtpErlangLong) posicao.elementAt(1)).intValue();
				Posicao origem = new Posicao(x, y);
				posicao = (OtpErlangTuple) msg.elementAt(2);
				x = ((OtpErlangLong) posicao.elementAt(0)).intValue();
				y = ((OtpErlangLong) posicao.elementAt(1)).intValue();
				Posicao destino = new Posicao(x, y);
				saida.criaturaMoveu(origem, destino);
			} catch (ClassCastException e) {
				throw new SimuladorErlangException("Tipo inválido ao tentar obter posicoes de movimentação.", e);
			} catch (OtpErlangRangeException e) {
				throw new SimuladorErlangException("Não foi possivel obter a posicao da criatura", e);
			}
		}
	};

	private static final Map<String, ProcessadorMensagem> mapaProcessador = new HashMap<String, ProcessadorMensagem>();

	static {
		for (ProcessadorMensagem proc : ProcessadorMensagem.values()) {
			mapaProcessador.put(proc.id, proc);
		}
	}

	private final String id;

	private ProcessadorMensagem(String id) {
		this.id = id;
	}

	public abstract void processarMensagem(OtpErlangTuple msg, SaidaSimulacao saida, SimuladorErlang simulador);

	public static ProcessadorMensagem getProcessador(String id) {
		return mapaProcessador.get(id);
	}
}
